package com.base.components.common.listener.heartbeat;

import com.base.components.common.boot.Profiles;
import com.base.components.common.constants.sys.Dates;
import com.base.components.common.dto.sys.DiscoveryAlarm;
import com.base.components.common.service.sys.MailUtil;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.netflix.appinfo.EurekaInstanceConfig;
import com.netflix.discovery.DiscoveryClient;
import com.netflix.discovery.EurekaClient;
import org.apache.commons.lang3.ObjectUtils;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.util.ProxyUtils;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.lang.invoke.MethodHandles;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * DiscoveryClientHeartbeatListener 监听注册中心与当前服务的心跳
 *
 * @author <a href="drakelee1221@gmail.com">LiGeng</a>
 * @version v1.0.0
 * @date 2019-03-19 16:31
 */
@Component
@ConditionalOnBean({EurekaClient.class, EurekaInstanceConfig.class})
@ConditionalOnMissingClass("org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceCanceledEvent")
@ConfigurationProperties("base.discovery.listener")
public class DiscoveryClientHeartbeatListener {
  private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
  /** 启动时，使用此 profile 来强制开启 */
  public static final String LISTENER_PROFILE = "listener";
  @Autowired
  private EurekaClient discoveryClient;
  @Autowired
  private EurekaInstanceConfig eurekaInstanceConfig;
  @Autowired
  private MailUtil mailUtil;

  private volatile DiscoveryClient unwrap;

  private ScheduledExecutorService scheduledExecutorService;

  private DiscoveryHeartbeat heartbeat;

  private DiscoveryAlarm alarm;

  private AtomicBoolean alarmed = new AtomicBoolean(false);

  private long timeoutMs;

  private boolean enable;

  @PostConstruct
  public void init(){
    if(!enable){
      if(Profiles.contains(LISTENER_PROFILE)){
        logger.info("base.discovery.listener.enable = false, but has profile > {}", LISTENER_PROFILE);
      }
      else {
        return;
      }
    }
    unwrap();
    if(ObjectUtils.allNotNull(unwrap, heartbeat, alarm)){
      timeoutMs = TimeUnit.MILLISECONDS.convert(heartbeat.getTimeout() > eurekaInstanceConfig.getLeaseExpirationDurationInSeconds()
                                              ? heartbeat.getTimeout()
                                              : eurekaInstanceConfig.getLeaseExpirationDurationInSeconds(), TimeUnit.SECONDS);
      scheduledExecutorService = new ScheduledThreadPoolExecutor(
        1, new ThreadFactoryBuilder().setDaemon(true).setNameFormat(getClass().getSimpleName()).build());
      scheduledExecutorService.scheduleWithFixedDelay(this::checkHeartbeat, heartbeat.getCheckLimit(), heartbeat.getCheckLimit(), TimeUnit.SECONDS);
      logger.info("DiscoveryClientHeartbeatListener initialized !");
    }
  }

  public ScheduledExecutorService getScheduledExecutorService(){
    return this.scheduledExecutorService;
  }

  private void checkHeartbeat(){
    if(unwrap != null){
      if (unwrap.getLastSuccessfulHeartbeatTimePeriod() >= timeoutMs && !alarmed.get()) {
        alarmed.set(true);
        alarm();
      }
      else if (unwrap.getLastSuccessfulHeartbeatTimePeriod() < timeoutMs && alarmed.get()) {
        alarmed.set(false);
        resume();
      }
    }
  }

  private void unwrap(){
    if(discoveryClient instanceof DiscoveryClient){
      this.unwrap = (DiscoveryClient) discoveryClient;
    }
    else {
      try {
        EurekaClient target = ProxyUtils.getTargetObject(discoveryClient);
        if(target instanceof DiscoveryClient){
          this.unwrap =  (DiscoveryClient) target;
        }
      } catch (Exception ignore) {
      }
    }
  }

  private void resume(){
    doNotify("Heartbeat Resume");
  }

  private void alarm() {
    doNotify("Heartbeat Timeout");
  }

  private void doNotify(String subject){
    String instanceId = eurekaInstanceConfig.getInstanceId();
    DiscoveryAlarm.NotifyMailbox mailbox = alarm.getNotifyMailbox();
    DateTime last = new DateTime(System.currentTimeMillis() - unwrap.getLastSuccessfulHeartbeatTimePeriod());
    String content = "LastSuccess:\n>　" + last.toString(Dates.DATE_TIME_FORMATTER_PATTERN) + "\nServiceId:\n>　" + instanceId;
    logger.warn("Discovery Alarm > " + subject + " > " + content);
    if(mailbox != null){
      try {
        mailUtil.sendMail(mailbox, subject + " > " + instanceId, content, mailbox.getTargetAddresses());
      } catch (Exception e) {
        logger.error("mail error", e);
      }
    }
  }

  public DiscoveryHeartbeat getHeartbeat() {
    return heartbeat;
  }

  public void setHeartbeat(DiscoveryHeartbeat heartbeat) {
    this.heartbeat = heartbeat;
  }

  public DiscoveryAlarm getAlarm() {
    return alarm;
  }

  public void setAlarm(DiscoveryAlarm alarm) {
    this.alarm = alarm;
  }

  public boolean isEnable() {
    return enable;
  }

  public void setEnable(boolean enable) {
    this.enable = enable;
  }
}
